using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Threading;

namespace csammisrun.MsnLib.Utility
{
  public delegate void NotificationConnectedHandler(Notification notification);
  public delegate void NotificationDisconnectedHandler(Notification notification);
  public delegate void NotificationConnectionFailedHandler(Notification notification, string error);

  public class Notification
  {
    private const string AUTHPAIRKEY = "msmsgs@msnmsgr.com";
    private const string AUTHPAIRVALUE = "Q1P7W2E4J9R8U3S5";
    private Session _parent = null;
    private CommandProcessor _proc = null;
    private ServerConnection _conn = null;
    private bool _inuse = false;
    private Timer _pngtimer = null;
    private bool _alreadysentloginstep = false;

    // Used during contactlist sync
    private int _totalusers = 0;
    private int _receivedusers = 0;
    private int _totalgroups = 0;
    private int _receivedgroups = 0;
    private User _lastuserreceived = null;

    /// <summary>
    /// Raised when the notification object could not complete its connection
    /// </summary>
    public event NotificationConnectionFailedHandler ConnectionFailed;
    /// <summary>
    /// Raised when the notification object has been disconnected
    /// </summary>
    public event NotificationDisconnectedHandler Disconnected;

    public Notification(Session parent)
    {
      _parent = parent;
      _conn = new ServerConnection(_parent, ServerConnectionType.NameServer);
      _conn.Connected += new ServerConnectionEventHandler(_conn_Connected);
      _conn.ConnectionLost += new ServerConnectionErrorHandler(_conn_ConnectionLost);
      _conn.Disconnected += new ServerConnectionEventHandler(_conn_Disconnected);
      _proc = _conn.CommandProc;
      _proc.Data = this;

      _pngtimer = new Timer(new TimerCallback(PngTimerCallback), null, Timeout.Infinite, 30000);

      InitProcessorRegistry();
    }

    #region Connection logic
    /// <summary>
    /// Connects the nameserver to the specified host and port
    /// </summary>
    public bool Connect(string host, int port)
    {
      return _conn.Connect(host, port);
    }

    /// <summary>
    /// Disconnects the notification object and its underlying connections
    /// </summary>
    public void Disconnect()
    {
      if (_conn.IsConnected)
      {
        // Send an OUT message directly through the connection
        Logging.WriteString("Sending OUT for notification board");
        _conn.SendPacket(Encoding.UTF8.GetBytes("OUT\r\n"));
        Logging.WriteString("Disconnecting notification board");
        _conn.Disconnect();
      }
    }

    /// <summary>
    /// Fired when the ServerConnection returns a valid connection
    /// </summary>
    void _conn_Connected(ServerConnection conn)
    {
      // Send the VER command to the server
      string[] args = new string[Session.PROTOCOL_VERSION - 8 + 2];
      for (int i = Session.PROTOCOL_VERSION; i >= 8; i--)
      {
        args[Session.PROTOCOL_VERSION - i] = String.Format("MSNP{0}", i);
      }
      args[args.Length - 1] = "CVR0";
      //args[args.Length - 1] = "";

      _proc.SendCommand("VER", "{0}", String.Join(" ", args));

      // The Connected event is supposed to be fired here, but
      // we're not truly connected yet. Handshaking just started.
      if (_alreadysentloginstep == false)
      {
        // Only fire this once, xfrs can occur
        _parent.OnLoginProgressUpdated(LoginStep.StepOneConnectedToServer);
        _alreadysentloginstep = true;
      }

      _inuse = conn.IsConnected;
    }

    void _conn_ConnectionLost(ServerConnection conn, string error)
    {
      if (InUse)
      {
        _inuse = false;
      }

      if (this.ConnectionFailed != null)
      {
        this.ConnectionFailed(this, error);
      }

      // Call the disconnected handler too, okay why not
      _conn_Disconnected(_conn);
    }

    void _conn_Disconnected(ServerConnection conn)
    {
      if (InUse)
      {
        _inuse = false;

        if (this.Disconnected != null)
        {
          this.Disconnected(this);
        }
      }
    }
    #endregion

    #region Public methods
    /// <summary>
    /// Add a contact to a list
    /// </summary>
    public void AddContact(string passport, string alias, int groupid, UserListType list)
    {
      if (groupid < 0 && list == UserListType.Forward)
      {
        groupid = 0;
      }

      if (groupid >= 0)
      {
        _proc.SendCommand("ADD", "{0} {1} {2} {3}",
          UserList.GetListNameByType(list),
          passport, alias, groupid);
      }
      else
      {
        _proc.SendCommand("ADD", "{0} {1} {2}",
          UserList.GetListNameByType(list),
          passport, alias);
      }
    }

    /// <summary>
    /// Remove a contact from a list
    /// </summary>
    public void RemoveContact(string passport, int groupid, UserListType list)
    {
      if (groupid >= 0)
      {
        _proc.SendCommand("REM", "{0} {1} {2}",
          UserList.GetListNameByType(list),
          passport, groupid);
      }
      else
      {
        _proc.SendCommand("REM", "{0} {1}", UserList.GetListNameByType(list), passport);
      }
    }

    /// <summary>
    /// Sets the current status of the logged-in user
    /// </summary>
    public void SetCurrentStatus(UserStatus status)
    {
      if (!this.InUse)
      {
        return;
      }

      if (_parent.Self.Object == null)
      {
        _proc.SendCommand("CHG", "{0} {1}", User.GetStatusNameFromStatus(status), _parent.GetClientID());
      }
      else
      {
        _proc.SendCommand("CHG", "{0} {1} {2}", User.GetStatusNameFromStatus(status),
          _parent.GetClientID(), _parent.Self.Object.ToString());
      }
    }

    /// <summary>
    /// Start the keepalive timer to send PNG packets every 30 seconds
    /// </summary>
    public void StartKeepaliveTimer()
    {
      _pngtimer.Change(30000, 30000);
    }
    #endregion

    private void InitProcessorRegistry()
    {
      CommandRegistrar cr = _proc.Registry;
      cr.Clear();

      cr.RegisterHandlerForServerResponse("CHG", "CHG", null);
      cr.RegisterHandlerForServerResponse("CHG", "ILN", new CommandHandler(IlnCmd));
      cr.RegisterHandlerForServerResponse("ADD", "ADD", new CommandHandler(AddCmd));
      cr.RegisterHandlerForServerResponse("ADD", "ILN", new CommandHandler(IlnCmd));
      cr.RegisterHandlerForServerResponse("REM", "REM", new CommandHandler(RemCmd));
      cr.RegisterHandlerForServerResponse("USR", "USR", new CommandHandler(UsrCmd));
      cr.RegisterHandlerForServerResponse("USR", "XFR", new CommandHandler(XfrCmd));
      cr.RegisterHandlerForServerResponse("SYN", "SYN", new CommandHandler(SynCmd));
      cr.RegisterHandlerForServerResponse("CVR", "CVR", new CommandHandler(CvrCmd));
      cr.RegisterHandlerForServerResponse("VER", "VER", new CommandHandler(VerCmd));
      cr.RegisterHandlerForServerResponse("REA", "REA", new CommandHandler(ReaCmd));
      cr.RegisterHandlerForServerResponse("PRP", "PRP", null); // Explicitly ignoring this
      cr.RegisterHandlerForServerResponse("BLP", "BLP", null);
      cr.RegisterHandlerForServerResponse("REG", "REG", new CommandHandler(RegCmd));
      cr.RegisterHandlerForServerResponse("ADG", "ADG", new CommandHandler(AddGroupCmd));
      cr.RegisterHandlerForServerResponse("RMG", "RMG", new CommandHandler(RemGroupCmd));
      cr.RegisterHandlerForServerResponse("XFR", "XFR", new CommandHandler(XfrCmd));

      cr.RegisterHandlerForServerCommand("IPG", new CommandHandler(IpgCmd));
      cr.RegisterHandlerForServerCommand("MSG", new CommandHandler(MsgCmd));
      cr.RegisterHandlerForServerCommand("NOT", new CommandHandler(NotCmd));
      cr.RegisterHandlerForServerCommand("CHL", new CommandHandler(ChlCmd));
      cr.RegisterHandlerForServerCommand("REM", new CommandHandler(RemCmd));
      cr.RegisterHandlerForServerCommand("ADD", new CommandHandler(AddCmd));
      cr.RegisterHandlerForServerCommand("QRY", null);
      cr.RegisterHandlerForServerCommand("QNG", null);
      cr.RegisterHandlerForServerCommand("FLN", new CommandHandler(FlnCmd));
      cr.RegisterHandlerForServerCommand("NLN", new CommandHandler(NlnCmd));
      cr.RegisterHandlerForServerCommand("ILN", new CommandHandler(IlnCmd));
      cr.RegisterHandlerForServerCommand("OUT", new CommandHandler(OutCmd));
      cr.RegisterHandlerForServerCommand("RNG", new CommandHandler(RngCmd));
      cr.RegisterHandlerForServerCommand("URL", new CommandHandler(UrlCmd));
      // libgaim puts these in a sync object, but I think the "context switch" is a bit pointless
      // and could potentially result in a lost message
      cr.RegisterHandlerForServerCommand("GTC", new CommandHandler(GtcCmd));
      cr.RegisterHandlerForServerCommand("BLP", new CommandHandler(BlpCmd));
      cr.RegisterHandlerForServerCommand("PRP", new CommandHandler(PrpCmd));
      cr.RegisterHandlerForServerCommand("LSG", new CommandHandler(LsgCmd));
      cr.RegisterHandlerForServerCommand("LST", new CommandHandler(LstCmd));
      cr.RegisterHandlerForServerCommand("BPR", new CommandHandler(BprCmd));

      cr.RegisterHandlerForError("ADD", new CommandErrorHandler(AddError));
      cr.RegisterHandlerForError("REG", new CommandErrorHandler(RegError));
      cr.RegisterHandlerForError("RMG", new CommandErrorHandler(RmgError));
      cr.RegisterHandlerForError("USR", new CommandErrorHandler(UsrError));

      cr.RegisterHandlerForMessage("text/x-msmsgsprofile", new MessageHandler(ProfileMsg));
      cr.RegisterHandlerForMessage("text/x-msmsgsinitialemailnotification", new MessageHandler(InitialEmailMsg));
      cr.RegisterHandlerForMessage("text/x-msmsgsemailnotification", new MessageHandler(EmailMsg));
      cr.RegisterHandlerForMessage("text/x-msmsgssystemmessage", new MessageHandler(SystemMsg));
      
      //cr.RegisterHandlerForCommand(fallback, "XFR", null);
    }

    #region Command handlers
    private void CvrCmd(CommandProcessor processor, MsnCommand command)
    {
      processor.SendCommand("USR", "TWN I {0}", _parent.Username);
    }

    /// <summary>
    /// Responds to a authentication challenge
    /// </summary>
    private void UsrCmd(CommandProcessor processor, MsnCommand command)
    {
      string[] elements = null;
      string[] tokens = null;

      if (command.Parameters[1] == "OK")
      {
        // Our response to the challenge was accepted
        _parent.Self = new User(_parent.Userlist, command.Parameters[2], command.Parameters[3]);
        processor.SendCommand("SYN", "{0}", "0");
      }
      else if (command.Parameters[1] == "TWN")
      {
        // Read in challenge parameters and give them to the nexus
        elements = command.Parameters[3].Split(',');
        foreach (string s in elements)
        {
          tokens = s.Split('=');
          _parent.Nexus.ChallengeData.Add(tokens[0], tokens[1]);
        }

        // The parent session will respond to nexus error events,
        // so just attach a successful authentication delegate here
        _parent.Nexus.Authenticated += delegate(Nexus nxs, string prms)
        {
          _parent.OnLoginProgressUpdated(LoginStep.StepFourAuthenticated);
          _proc.SendCommand("USR", "TWN S {0}", prms);
        };
        Logging.WriteString("Passing control to nexus");
        _parent.Nexus.Connect(command.Parameters[3]);
      }
    }

    /// <summary>
    /// Processes a command to transfer the nameserver connection to another server
    /// </summary>
    private void XfrCmd(CommandProcessor processor, MsnCommand command)
    {
      string[] tokens = command.Parameters[2].Split(':');

      if (command.Parameters[1] == "NS")
      {
        processor.Connection.Connect(tokens[0], Int32.Parse(tokens[1]));
      }
      else if (command.Parameters[1] == "SB")
      {
        // We requested a switchboard, here's the auth data...huh?
        if (command.Reference != null && command.Reference.Data is Switchboard)
        {
          Switchboard newboard = command.Reference.Data as Switchboard;
          newboard.AuthKey = command.Parameters[4];
          newboard.ConnectToServer(tokens[0], Int32.Parse(tokens[1]));
        }
      }
      else
      {
        Logging.WriteString("Received a XFR for an unknown object: " + command.Parameters[1]);
      }
    }

    /// <summary>
    /// Receive a list of server-supported protocols, respond with our app version
    /// </summary>
    private void VerCmd(CommandProcessor processor, MsnCommand command)
    {
      string ourversion = "";
      bool versionsupported = false;

      // Check to see that MsnLib's protocol version is still supported
      // by the server.  God help us all.
      ourversion = String.Format("MSNP{0}", Session.PROTOCOL_VERSION);
      foreach (string param in command.Parameters)
      {
        if (param == ourversion)
        {
          versionsupported = true;
          break;
        }
      }

      if (!versionsupported)
      {
        _parent.OnError(MsnErrorCode.ProtocolVersionNotSupported);
        return;
      }

      // Respond with our app version, fakin' the MSN funk here
      processor.SendCommand("CVR", "0x0409 winnt 5.1 i386 MSNMSGR 6.0.0602 MSMSGS {0}", _parent.Username);
    }

    /// <summary>
    /// Occurs in response to an ADD transaction
    /// </summary>
    private void AddCmd(CommandProcessor processor, MsnCommand command)
    {
      // Move to DoUserUpdate?
      string list = "";
      string passport = "";
      string friendly = "";
      User user = null;
      UserListType type;
      int groupid = -1;

      list = command.Parameters[1];
      type = UserList.GetListTypeByName(list);
      passport = command.Parameters[3];
      friendly = command.Parameters[4];
      if (command.Parameters.Count > 5)
      {
        groupid = Int32.Parse(command.Parameters[5]);
      }

      user = _parent.Userlist.FindUserByPassport(passport);
      if (user == null)
      {
        user = new User(_parent.Userlist, passport, friendly);
        switch (type)
        {
          case UserListType.Allow:
            // Not currently used
            break;
          case UserListType.Block:
            // Not currently used
            break;
          case UserListType.Forward:
            _parent.Userlist.Users.Add(user);
            _parent.OnUserStatusUpdateReceived(user);
            break;
          case UserListType.Reverse:
            _parent.PendingUsers.Users.Add(user);
            _parent.OnUserListAdditionRequest(user);
            break;
        }
        
      }
      else
      {
        user.DisplayName = friendly;
        _parent.OnUserStatusUpdateReceived(user);
      }
    }

    private void AddGroupCmd(CommandProcessor processor, MsnCommand command)
    {
      int groupid = -1;
      string groupname = "";
      Group group = null;

      groupname = command.Parameters[2];
      groupid = Int32.Parse(command.Parameters[3]);
      group = new Group(groupid, groupname);
      _parent.Userlist.Groups.Add(group);

      // gaim does some logic to add users into that group as a result of a transaction
      // that we haven't implemented yet
    }

    /// <summary>
    /// Processes the result of changing our own friendly name
    /// </summary>
    private void ReaCmd(CommandProcessor processor, MsnCommand command)
    {
      _parent.SetFriendlyName(command.Parameters[3]);
    }

    /// <summary>
    /// Processes the result of a group rename operation
    /// </summary>
    private void RegCmd(CommandProcessor processor, MsnCommand command)
    {
      int groupid = -1;
      string groupname = "";
      Group group = null;

      groupid = Int32.Parse(command.Parameters[2]);
      groupname = command.Parameters[3];

      group = _parent.Userlist.FindGroupByID(groupid);
      if (group != null)
      {
        group.DisplayName = groupname;
      }
    }

    /// <summary>
    /// Processes a "remove user" command
    /// </summary>
    private void RemCmd(CommandProcessor processor, MsnCommand command)
    {
      UserListType listtype;
      User user = null;
      int groupid = -1;

      user = _parent.Userlist.FindUserByPassport(command.Parameters[3]);
      listtype = UserList.GetListTypeByName(command.Parameters[1]);
      if (user == null)
      {
        return;
      }

      if (command.Parameters.Count == 5)
      {
        groupid = Int32.Parse(command.Parameters[4]);
      }

      // Do user removal

      _parent.OnUserStatusUpdateReceived(user);
    }

    /// <summary>
    /// Processes a "remove group" command
    /// </summary>
    private void RemGroupCmd(CommandProcessor processor, MsnCommand command)
    {
      int groupid = Int32.Parse(command.Parameters[2]);
      // Do group removal
    }

    private void SynCmd(CommandProcessor processor, MsnCommand command)
    {
      _totalusers = Int32.Parse(command.Parameters[2]);
      _totalgroups = Int32.Parse(command.Parameters[3]);
      _receivedgroups = _receivedusers = 0;

      if (_totalusers == 0 && _totalgroups == 0)
      {
        _parent.OnLoginComplete();
      }
      else
      {
        _parent.Userlist.IsSyncing = true;
        _parent.OnLoginProgressUpdated(LoginStep.StepFiveSyncing);
        // At this point sync messages (LST, LSG) begin coming in
      }
    }

    #endregion

    #region Server-raised command handlers
    /// <summary>
    /// The server's kicking us off
    /// </summary>
    private void OutCmd(CommandProcessor processor, MsnCommand command)
    {
      if (command.Parameters[0] == "OTH")
      {
        // Some other goof signed on with the same username
        _parent.OnError(MsnErrorCode.SignedInElsewhere);
      }
      else if (command.Parameters[0] == "SSD")
      {
        // Server's going down
        _parent.OnError(MsnErrorCode.ServerShuttingDown);
      }
    }

    private void MsgCmd(CommandProcessor processor, MsnCommand command)
    {
      //if (command.Payload == null)
      //{
      //  processor.Connection.SetExpectedPayloadLength(Int32.Parse(command.Parameters[2]));
      //}
      //else
      //{
        Message msg = new Message(_parent, command);
        msg.ParsePayload(command.Payload);
        msg.RemoteUser = command.Parameters[0];
        processor.ProcessMessage(msg);
      //}
    }

    /// <summary>
    /// Respond to a challenge
    /// </summary>
    private void ChlCmd(CommandProcessor processor, MsnCommand command)
    {
      byte[] buffer = Encoding.UTF8.GetBytes(command.Parameters[1] + AUTHPAIRVALUE);
      byte[] digest = MD5.Create().ComputeHash(buffer);
      StringBuilder payload = new StringBuilder();
      for (int i = 0; i < 16; i++)
      {
        payload.AppendFormat("{0:x2}", digest[i]);
      }

      Transaction trans = new Transaction(processor, "QRY", "{0} 32", AUTHPAIRKEY);
      trans.Payload = Encoding.UTF8.GetBytes(payload.ToString());
      processor.SendTransaction(trans);
    }

    private void FlnCmd(CommandProcessor processor, MsnCommand command)
    {
      User user = _parent.Userlist.FindUserByPassport(command.Parameters[0]);
      user.Status = UserStatus.Offline;
      _parent.OnUserStatusUpdateReceived(user);

      // Destroy SLP link if one exists, which it doesn't because hahahaha
    }

    private void IlnCmd(CommandProcessor processor, MsnCommand command)
    {
      DoUserUpdate(command, 1);
    }

    private void NlnCmd(CommandProcessor processor, MsnCommand command)
    {
      DoUserUpdate(command, 0);
    }

    /// <summary>
    /// Sets up the connection to receive an incoming page
    /// </summary>
    private void IpgCmd(CommandProcessor processor, MsnCommand command)
    {
    }

    private void NotCmd(CommandProcessor processor, MsnCommand command)
    {
    }

    /// <summary>
    /// Someone rang us up, guvner!
    /// </summary>
    private void RngCmd(CommandProcessor processor, MsnCommand command)
    {
      Switchboard sb = null;
      int port = 0;
      
      sb = new Switchboard(_parent, SwitchboardType.Conversation);
      // At the moment, this is thought to be a InstantMessage
      sb.SubType = SwitchboardSubType.InstantMessage;
      sb.HasBeenInvited = true;
      sb.SessionID = command.Parameters[0];
      string[] tokens = command.Parameters[1].Split(':');
      sb.AuthKey = command.Parameters[3];
      sb.IMUser = command.Parameters[4];  // What the hell is im_user?  The inviter?
      if (_parent.UseHTTP)
      {
        port = 80;
      }
      else
      {
        port = Int32.Parse(tokens[1]);
      }
      sb.ConnectToServer(tokens[0], port);
    }

    private void UrlCmd(CommandProcessor processor, MsnCommand command)
    {

    }

    #region Sync list server commands
    /// <summary>
    /// Don't rightly know...
    /// </summary>
    private void GtcCmd(CommandProcessor processor, MsnCommand command)
    {
    }

    /// <summary>
    /// Gets the default visibility setting from the server
    /// </summary>
    private void BlpCmd(CommandProcessor processor, MsnCommand command)
    {
      UserListType listtype = UserList.GetListTypeByName(command.Parameters[0]);
      if (listtype == UserListType.Allow)
      {
        // Allow by default
        // Only users in the deny list will be blocked
      }
      else if (listtype == UserListType.Block)
      {
        // Block by default
        // Only users in the allow list will be let in
      }
    }

    /// <summary>
    /// Process a property on the local user
    /// </summary>
    private void PrpCmd(CommandProcessor processor, MsnCommand command)
    {
      switch (command.Parameters[0])
      {
        case "PHH":  // Home phone
          break;
        case "PHW":  // Work phone
          break;
        case "PHM":  // Mobile phone
          break;
        case "MOB":
          break;
        case "MBE":
          break;
        case "WWE":
          break;
      }
    }

    /// <summary>
    /// Process a property on the last user entry received
    /// </summary>
    private void BprCmd(CommandProcessor processor, MsnCommand command)
    {
      if(_lastuserreceived != null)
      {
        switch (command.Parameters[0])
        {
          case "PHH":  // Home phone
            break;
          case "PHW":  // Work phone
            break;
          case "PHM":  // Mobile phone
            break;
          case "MOB":
            break;
          case "MBE":
            break;
          case "WWE":
            break;
        }
      }
    }

    /// <summary>
    /// Process a contact list group entry
    /// </summary>
    private void LsgCmd(CommandProcessor processor, MsnCommand command)
    {
      int groupid = Int32.Parse(command.Parameters[0]);
      string groupname = command.Parameters[1];

      Group newgroup = new Group(groupid, groupname);
      _parent.OnGroupItemReceived(newgroup);

      IncrementGroupCount();
      CheckForSyncComplete();
    }

    /// <summary>
    /// Process a contact list user entry
    /// </summary>
    private void LstCmd(CommandProcessor processor, MsnCommand command)
    {
      string passport = command.Parameters[0];
      string friendly = command.Parameters[1];
      ListOperationType ops = (ListOperationType)Int32.Parse(command.Parameters[2]);

      User user = new User(_parent.Userlist, passport, friendly);
      if ((ops & ListOperationType.ForwardList) != 0)
      {
        string[] groups = command.Parameters[3].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
        foreach (string group in groups)
        {
          user.Groups.Add(_parent.Userlist.FindGroupByID(Int32.Parse(group)));
        }

        _parent.OnUserItemReceived(user);
      }
      else if(ops == ListOperationType.ReverseList)
      {
        _parent.OnUserListAdditionRequest(user);
      }

      // Set to catch property updates on this entry
      _lastuserreceived = user;

      IncrementUserCount();
      CheckForSyncComplete();
    }
    #endregion

    #endregion

    #region Message handlers
    private void ProfileMsg(CommandProcessor processor, Message msg)
    {
      if (msg.RemoteUser != "Hotmail")
      {
        return;
      }

      int intval = 0;
      long longval = 0;

      _parent.PassportInfo.Kv = msg.Attributes("kv");
      _parent.PassportInfo.Sid = msg.Attributes("sid");
      _parent.PassportInfo.Auth = msg.Attributes("MSPAuth");
      _parent.PassportInfo.ClientIP = msg.Attributes("ClientIP");
      Int32.TryParse(msg.Attributes("ClientPort"), out intval);
      long.TryParse(msg.Attributes("LoginTime"), out longval);
      _parent.PassportInfo.ClientPort = intval;
      _parent.PassportInfo.Sl = longval;
    }

    private void InitialEmailMsg(CommandProcessor processor, Message msg)
    {
      if (msg.RemoteUser != "Hotmail")
      {
        return;
      }

      if (String.IsNullOrEmpty(_parent.PassportInfo.Redirecter))
      {
        Transaction trans = new Transaction(processor, "URL", "{0}", "INBOX");
        trans.SetTriggeringCommand(msg.Command);
        processor.SendTransaction(trans);
        return;
      }

      int inboxunread = 0, foldersunread = 0;
      Int32.TryParse(msg.Attributes("Inbox-Unread"), out inboxunread);
      Int32.TryParse(msg.Attributes("Folders-Unread"), out foldersunread);

      _parent.OnInitialEmailNotificationReceived(inboxunread, foldersunread);
    }

    private void EmailMsg(CommandProcessor processor, Message msg)
    {
      if (msg.RemoteUser != "Hotmail")
      {
        return;
      }

      if (String.IsNullOrEmpty(_parent.PassportInfo.Redirecter))
      {
        Transaction trans = new Transaction(processor, "URL", "{0}", "INBOX");
        trans.SetTriggeringCommand(msg.Command);
        processor.SendTransaction(trans);
        return;
      }

      string from = "", subject = "";
      from = msg.Attributes("From");
      subject = msg.Attributes("Subject");
      _parent.OnEmailNotificationReceived(from, subject);
    }

    private void SystemMsg(CommandProcessor processor, Message msg)
    {
      if (msg.RemoteUser != "Hotmail")
      {
        return;
      }
    }
    #endregion

    #region Error handlers
    private void UsrError(CommandProcessor processor, Transaction trans, int error)
    {
      MsnErrorCode errorcode = MsnErrorCode.ServerUnavailable;
      switch (error)
      {
        case 911:
          errorcode = MsnErrorCode.AuthenticationFailed;
          break;
      }

      _parent.OnError(errorcode);
    }

    private void AddError(CommandProcessor processor, Transaction trans, int error)
    {
      UserListType listtype;
      string passport = "";
      // Do this gaim's way for now, but it should be moved to an enum later
      string reason = "";
      string msg = "";
      string[] parameters = null;

      parameters = trans.Parameters.Split(' ');
      listtype = UserList.GetListTypeByName(parameters[0]);
      passport = parameters[1];

      switch (listtype)
      {
        case UserListType.Forward:
          msg = "Cannot add user to buddy list";
          if (error == 210)
          {
            reason = " because it is full";
          }
          break;
        case UserListType.Block:
          msg = "Cannot add user to block list";
          break;
        case UserListType.Allow:
          msg = "Cannot add user to allow list";
          break;
      }

      if (reason == "")
      {
        switch (error)
        {
          case 208:
            reason = String.Format(" because {0} is not a valid Passport account", passport);
            break;
          case 500:
            reason = " because the service is temporarily unavailable";
            break;
          default:
            reason = " because an unknown error occured";
            break;
        }
      }

      // Let the session know
    }

    private void RegError(CommandProcessor processor, Transaction trans, int error)
    {
      int groupid = Int32.Parse(trans.Parameters.Split(' ')[0]);

      // Let the session know
    }

    private void RmgError(CommandProcessor processor, Transaction trans, int error)
    {
      int groupid = Int32.Parse(trans.Parameters.Split(' ')[0]);

      // Let the session know
    }
    #endregion

    #region Utility methods
    private void PngTimerCallback(object state)
    {
      if (_proc.Connection.IsConnected)
      {
        _proc.SendCommand("PNG", "", null);
      }
    }
    /// <summary>
    /// ILN and NLN differ in their action by one parameter, so the functionality is here instead
    /// </summary>
    private void DoUserUpdate(MsnCommand command, int offset)
    {
      string passport = "";
      string friendly = "";
      string state = "";
      int clientid = 0;
      User user = null;
      MsnObject obj = null;

      state = command.Parameters[offset + 0];
      passport = command.Parameters[offset + 1];
      friendly = command.Parameters[offset + 2];
      clientid = Int32.Parse(command.Parameters[offset + 3]);
      if(Session.PROTOCOL_VERSION >= 9 && command.Parameters.Count > (offset + 4))
      {
        try
        {
          obj = new MsnObject(command.Parameters[offset + 4]);
        }
        catch { }
      }

      user = _parent.Userlist.FindUserByPassport(passport);
      user.DisplayName = friendly;
      user.Object = obj;
      user.IsMobile = ((clientid & 0x0040) != 0);
      user.Status = User.GetStatusFromName(state);
      _parent.OnUserStatusUpdateReceived(user);
    }

    /// <summary>
    /// Increments the group count in a monitor region
    /// </summary>
    private void IncrementGroupCount()
    {
      lock (this)
      {
        _receivedgroups++;
      }
    }

    /// <summary>
    /// Increments the user count in a monitor region
    /// </summary>
    private void IncrementUserCount()
    {
      lock (this)
      {
        _receivedusers++;
      }
    }

    /// <summary>
    /// Determine whether the sync operation has finished
    /// </summary>
    private void CheckForSyncComplete()
    {
      lock (this)
      {
        if (_receivedusers == _totalusers)
        {
          _parent.OnLoginComplete();
        }
      }
    }
    #endregion

    #region Properties
    /// <summary>
    /// Gets or sets a value indicating whether or not this nameserver is in use
    /// </summary>
    public bool InUse
    {
      get { return _inuse; }
      set { _inuse = value; }
    }

    /// <summary>
    /// Gets the command processor assigned to the notification board
    /// </summary>
    internal CommandProcessor Processor
    {
      get { return _proc; }
    }

    /// <summary>
    /// Gets the <see cref="Session"/> that owns the notification board
    /// </summary>
    public Session ParentSession
    {
      get { return _parent; }
    }
    #endregion
  }
}
